package top.guochenchen.mydb.backend.tm;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import top.guochenchen.mydb.backend.utils.Panic;
import top.guochenchen.mydb.backend.utils.Parser;
import top.guochenchen.mydb.common.Error;

public class TransactionManagerImpl implements TransactionManager {

    // XID文件头长度
    static final int LEN_XID_HEADER_LENGTH = 8;
    // 每个事务的占用长度
    private static final int XID_FIELD_SIZE = 1;

    // 事务的三种状态
    private static final byte FIELD_TRAN_ACTIVE   = 0;
	private static final byte FIELD_TRAN_COMMITTED = 1;
	private static final byte FIELD_TRAN_ABORTED  = 2;

    // 超级事务，永远为commited状态
    public static final long SUPER_XID = 0;

    static final String XID_SUFFIX = ".xid";
    
    private RandomAccessFile file;
    private FileChannel fc;
    private long xidCounter;
    private Lock counterLock;

    TransactionManagerImpl(RandomAccessFile raf, FileChannel fc) {
        this.file = raf;
        this.fc = fc;
        counterLock = new ReentrantLock();
        checkXIDCounter();
    }

    /**
     * 检查XID文件是否合法
     * 读取XID_FILE_HEADER中的xidcounter，根据它计算文件的理论长度，对比实际长度
     */
    private void checkXIDCounter() {
        long fileLen = 0;//准备记录文件实际长度。
        try {
            fileLen = file.length();//通过 RandomAccessFile 取得 .xid 文件当前字节数。
        } catch (IOException e1) {
            Panic.panic(Error.BadXIDFileException);//读取失败 panic（视为致命损坏）。
        }
        if(fileLen < LEN_XID_HEADER_LENGTH) {
            Panic.panic(Error.BadXIDFileException);//文件长度小于头部长度8字节 panic（视为致命损坏）。
        }

        ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH);//申请一个 8 字节缓冲区，用来读取文件头。
        try {
            fc.position(0);//把文件通道的位置移动到开头（偏移 0）。
            fc.read(buf);//从文件通道读取 8 字节数据到缓冲区。
        } catch (IOException e) {
            Panic.panic(e);//读取失败 panic（视为致命损坏）。
        }
        this.xidCounter = Parser.parseLong(buf.array());//将缓冲区中的 8 字节数据解析为 long 类型，并赋值给 xidCounter。
        long end = getXidPosition(this.xidCounter + 1);//计算“理论文件长度”。getXidPosition(x) 会返回事务 x 的状态字节偏移；对于“下一个事务号”（xidCounter + 1），它的起始偏移就是“已有所有事务状态区的末尾”。因此这个值应该等于“真实文件长度”。
        if(end != fileLen) {
            Panic.panic(Error.BadXIDFileException);//若理论长度与真实长度不一致，说明文件头记录的事务数与尾部状态区的长度对不上（可能截断或脏数据），判为坏文件并 panic。
        }
    }

    // 根据事务xid取得其在xid文件中对应的位置
    private long getXidPosition(long xid) {
        return LEN_XID_HEADER_LENGTH + (xid-1)*XID_FIELD_SIZE;
    }

    // 更新xid事务的状态为status
    private void updateXID(long xid, byte status) {
        long offset = getXidPosition(xid);//计算事务xid在xid文件中的偏移量
        byte[] tmp = new byte[XID_FIELD_SIZE];//分配 1 字节数组并写入状态值。
        tmp[0] = status;
        ByteBuffer buf = ByteBuffer.wrap(tmp);//用 ByteBuffer 包裹数组，NIO 需要缓冲区。将字节数组包装为 ByteBuffer 并写入文件。
        try {
            fc.position(offset);//将 FileChannel 定位到偏移处并执行写入。
            fc.write(buf);
        } catch (IOException e) {//写失败直接 panic（视为致命损坏）。
            Panic.panic(e);
        }
        try {
            fc.force(false);//fc.force(false)：强制刷盘，false 表示不强制同步文件元数据，只同步内容。任何异常同样 panic。
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    // 将XID加一，并更新XID Header
    //作用：分配完一个新事务后，内存和磁盘中的计数器一起自增、持久化。

    private void incrXIDCounter() {
        xidCounter ++;//内存里的 xidCounter 自增。
        ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));//把 long 转 8 字节，包成 ByteBuffer。
        try {
            fc.position(0);//把通道定位到文件头 0 偏移并写入 8 字节。
        } catch (IOException e) {
            Panic.panic(e);
        }
        try {
            fc.force(false);//立即 force(false) 刷盘，确保崩溃后也不丢计数。
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    // 开始一个事务，并返回XID
    public long begin() {
        counterLock.lock();                          // 加锁，避免并发分配重复xid
        try {
            long xid = xidCounter + 1;               // 新事务号 = 当前计数 + 1
            updateXID(xid, FIELD_TRAN_ACTIVE);       // 先把该事务的状态字节写成 ACTIVE 并刷盘
            incrXIDCounter();                        // 再把计数器+1并刷盘
            return xid;                              // 返回分配的事务号
        } finally {
            counterLock.unlock();                    // 释放锁
        }
    }

    // 提交XID事务
    public void commit(long xid) {
        updateXID(xid, FIELD_TRAN_COMMITTED);        // 写状态=COMMITTED并刷盘
    }

    // 回滚XID事务
    public void abort(long xid) {
        updateXID(xid, FIELD_TRAN_ABORTED);          // 写状态=ABORTED并刷盘
    }

    // 检测XID事务是否处于status状态
    private boolean checkXID(long xid, byte status) {
        long offset = getXidPosition(xid);           // 定位状态字节
        ByteBuffer buf = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]); // 1字节读缓冲
        try {
            fc.position(offset);                     // 定位到偏移
            fc.read(buf);                            // 读取1字节
        } catch (IOException e) {
            Panic.panic(e);
        }
        return buf.array()[0] == status;             // 比较读取到的状态是否等于目标
    }

    public boolean isActive(long xid) {
        if(xid == SUPER_XID) return false;           // 超级事务永远不active
        return checkXID(xid, FIELD_TRAN_ACTIVE);
    }

    public boolean isCommitted(long xid) {
        if(xid == SUPER_XID) return true;            // 超级事务永远committed
        return checkXID(xid, FIELD_TRAN_COMMITTED);
    }

    public boolean isAborted(long xid) {
        if(xid == SUPER_XID) return false;           // 超级事务永远不aborted
        return checkXID(xid, FIELD_TRAN_ABORTED);
    }

    public void close() {
        try {
            fc.close();                              // 先关通道
            file.close();                            // 再关底层文件
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

}
